<script setup lang="ts">
import {reactive, ref} from "vue";
import {checkUpload, initUpload, mergeChunk, uploadChunk } from "@/services/uploadApi.ts";
import SparkMD5 from "spark-md5";
import {Message} from "@arco-design/web-vue";

const emits = defineEmits<{
  (e: 'list'): void
}>()

const options = reactive({
  // 弹窗显示
  visible: false,
  // 是否显示进度
  progress: false,
  isPaused: false,
  currentChunkIndex: 0,
  uploadedFile: null as File | null, // 存储上传的文件
  fileHash: "", // 存储计算的文件 hash
})

// 文件上传相关状态
const fileList = ref<any[]>([]); // 上传文件列表
const uploading = ref(false); // 是否正在上传
const progress = ref(0); // 上传进度
const chunkSize = 2097152; // 定义分片大小：2MB
// 分片上传逻辑
const onFileChange = async (uploadedFile: File) => {
  // 重置状态
  uploading.value = true;

  progress.value = 0;

  // 保存文件至 options
  options.uploadedFile = uploadedFile;

  // 获取文件的 Hash 值
  const fileHash = await calculateFileHash(uploadedFile);
  options.fileHash = fileHash;
  // 检查文件是否已上传
  const checkResponse = await checkUpload({ fileHash });
  if (checkResponse.data.uploaded === true) {
    Message.warning('文件已存在，无需上传。')
    uploading.value = false;
    return;
  }

  options.progress = true

  const totalChunks = Math.ceil(uploadedFile.size / chunkSize); // 计算分片总数

  // 初始化上传
  await initUpload({
    fileName: uploadedFile.name,
    fileSize: uploadedFile.size,
    fileHash,
    chunkCount: totalChunks
  });

  // 开始分片上传
  try {
    await chunkUploadProcess(uploadedFile, fileHash);
    fileList.value.push(uploadedFile);
    Message.success({
      content: '上传成功'
    })
  } catch (err) {
    Message.error({
      content: '上传失败'
    })
  } finally {
    uploading.value = false;
  }
};
// 分片上传具体逻辑
async function chunkUploadProcess(file: File, fileHash: string) {
  const totalChunks = Math.ceil(file.size / chunkSize); // 计算分片总数

  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(file.size, (i + 1) * chunkSize);
    const chunk = file.slice(start, end); // 截取文件片段
    // 将 `Blob` 转为 `File`
    const chunkFile = new File([chunk], file.name, { type: file.type });

    // 计算分片的 hash 值
    const chunkHash = await calculateChunkHash(chunk);

    const formData = new FormData();
    formData.append("chunk", chunkFile);
    formData.append("chunkHash", chunkHash);
    formData.append("chunkIndex", i.toString());
    formData.append("fileHash", fileHash);

    // 调用上传分片接口
    await uploadChunk(formData);

    // 实时更新上传进度
    progress.value = Math.round(((i + 1) / totalChunks) * 100) / 100;
  }

  // 分片上传完成后，通知后端合并
  await mergeChunk({ fileHash });
}

// 计算文件 Hash
async function calculateFileHash(file: File): Promise<string> {
  return new Promise((resolve) => {
    const spark = new SparkMD5.ArrayBuffer();
    const reader = new FileReader();

    reader.onload = (e: ProgressEvent<FileReader>) => {
      if (e.target?.result) {
        spark.append(e.target.result as ArrayBuffer); // 增量计算 Hash
        resolve(spark.end()); // 获取最终的 MD5 Hash 值
      }
    };

    reader.readAsArrayBuffer(file); // 将文件读取为 ArrayBuffer
  });
}

// 计算单个分片的 Hash 值
async function calculateChunkHash(chunk: Blob): Promise<string> {
  return new Promise((resolve) => {
    const reader = new FileReader();
    const spark = new SparkMD5.ArrayBuffer();

    reader.onload = (e) => {
      if (e.target?.result) {
        spark.append(e.target.result as ArrayBuffer);
        resolve(spark.end()); // 返回 MD5 Hash 值
      }
    };

    reader.readAsArrayBuffer(chunk); // 读取分片为 ArrayBuffer
  });
}

const onDropFile = (event: DragEvent) => {
  event.preventDefault();

  // 如果用户拖入了文件
  const files = event.dataTransfer?.files;
  if (files && files.length > 0) {
    const uploadedFile = files[0]; // 这里只处理单个文件上传
    onFileChange(uploadedFile); // 直接调用分片上传逻辑
  }
};


const init = () => {
  options.visible = true
  options.progress = false
  fileList.value = []
  options.isPaused = false
  options.currentChunkIndex = 0
}

const handleOk = () => {
  options.visible = false
  emits('list')
}

defineExpose({
  init
})

const pauseUpload = () => {
  if (uploading.value) {
    options.isPaused = true;
    Message.info('上传已暂停！');
  } else {
    Message.warning('当前无上传任务！');
  }
}

const resumeUpload = async () => {
  if (!options.isPaused) {
    Message.warning('上传未暂停，无法继续！');
    return;
  }

  options.isPaused = false;
  uploading.value = true;
  // 使用存储的文件和文件 hash 继续上传
  await chunkUploadProcess(options.uploadedFile as File, options.fileHash);
}


// 网络监听
window.addEventListener('offline', () => {
  if (uploading.value) {
    pauseUpload();
    Message.warning('网络已断开，上传已暂停！');
  }
});

window.addEventListener('online', () => {
  Message.info('网络已恢复，可以继续上传。');
});
</script>

<template>
  <a-modal
      title="分片上传文件"
      :visible="options.visible"
      @cancel="options.visible = false"
      @ok="handleOk"
  >
    <div style="height: 300px; overflow: hidden;">
      <div style="overflow: auto;display: flex; flex-direction: column; gap: 12px">
        <!-- 上传区域 -->
        <div class="upload-area" @dragover.prevent @drop.prevent="onDropFile">
          <input
              type="file"
              class="file-input"
              id="fileInput"
              @change="e => e.target?.files?.[0] && onFileChange(e.target.files[0])"
          />

          <label for="fileInput" class="upload-label">
            点击选择或拖拽文件上传
          </label>
        </div>
        <a-progress v-if="options.progress" :percent="progress" />
        <div>
          <span>文件列表</span>
          <!-- 文件列表 -->
          <a-list
              v-if="fileList.length > 0"
              bordered
              size="small"
              class="file-list"
          >
            <a-list-item v-for="item in fileList" :extra="item.size">{{ item.name }}</a-list-item>
          </a-list>
        </div>
      </div>
    </div>
  </a-modal>
</template>

<style scoped lang="scss">
.upload-area {
  border: 2px dashed #b9c0d4;
  border-radius: 12px;
  //padding: 20px;
  text-align: center;
  background-color: #f9fafc;
  cursor: pointer;
}

.upload-area:hover {
  background-color: #eef2f5;
}

.file-input {
  display: none;
}

.upload-label {
  padding: 20px;
  box-sizing: border-box;
  display: block;
  width: 100%;
  font-size: 14px;
  color: #5b6270;
  cursor: pointer;
}
</style>